#include "gfx.h"

#ifdef USE_API_GFX_SDL_GL

#ifdef UGLY_HACK_FOR_THOSE_WHO_DONT_KNOW_HOWTO_INSTALL_SDL
#include <SDL.h>
#include <SDL_opengl.h>
#else
#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h>
#endif

#define MAX_TEXTURES 16

static GFX_Resolution _gfx_resolution;

static mat3 _gfx_matrix_pile[32];   // pile rassemblant toute les matris
static int _gfx_matrix_pile_top;    // position dans la pile de matris
static mat3 _gfx_matrix_current;    // matris courante
static float _gfx_blur;

// Texture manager

typedef struct
{
    char filename[64];
    GLuint texID;
    int srcW, srcH, texW, texH;
} _gfx_texture;

static _gfx_texture _gfx_textures[MAX_TEXTURES];

int _gfx_tex_next_power_2(int val);
void _gfx_tex_load(const char *filename);
_gfx_texture* _gfx_tex_get(const char *filename);
void _gfx_tex_bind(const char *filename);

void _gfx_init_opengl(void);

int _gfx_tex_next_power_2(int val)
{
    val |= (val >> 1);
    val |= (val >> 2);
    val |= (val >> 4);
    val |= (val >> 8);
    val |= (val >> 16);
    return val;
}

void _gfx_tex_load(const char *filename)
{
    // Find free texture slot
    _gfx_texture *tex = 0;
    for(int i = 0; i < MAX_TEXTURES; i++)
    {
        if(_gfx_textures[i].filename[0] == '\0')
        {
            tex = &_gfx_textures[i];
            break;
        }
    }
    if(tex == 0)
    {
        puts("ERR:Out of textures!");
        exit(EXIT_FAILURE);
    }

    // Load image
    SDL_Surface *img_surf = SDL_LoadBMP(filename);
    if(img_surf == 0)
    {
        printf("ERR:Couldn't load '%s' : %s\n", filename, SDL_GetError());
        exit(EXIT_FAILURE);
    }

    // Prepare data for OpenGL
    SDL_Surface *gl_surf = SDL_CreateRGBSurface(SDL_SWSURFACE,
                                                _gfx_tex_next_power_2(img_surf->w), _gfx_tex_next_power_2(img_surf->h),
                                                32, 0, 0, 0, 0);
    if(gl_surf == 0)
    {
        SDL_FreeSurface(img_surf);
        puts("ERR:Couldn't create temporary surface");
        exit(EXIT_FAILURE);
    }
    SDL_BlitSurface(img_surf, 0, gl_surf, 0);

    glGenTextures(1, &tex->texID);
    strcpy(tex->filename, filename);
    tex->srcW = img_surf->w;
    tex->srcH = img_surf->h;
    tex->texW = gl_surf->w;
    tex->texH = gl_surf->h;

    // Load to OpenGL
    glEnable(GL_TEXTURE_2D);
    SDL_LockSurface(gl_surf);
    glBindTexture(GL_TEXTURE_2D, tex->texID);

    glTexImage2D(GL_TEXTURE_2D, 0, 4, gl_surf->w, gl_surf->h, 0, GL_BGRA, GL_UNSIGNED_BYTE, gl_surf->pixels);

    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

    glDisable(GL_TEXTURE_2D);

    SDL_UnlockSurface(gl_surf);
    SDL_FreeSurface(gl_surf);
    SDL_FreeSurface(img_surf);
}

_gfx_texture* _gfx_tex_get(const char *filename)
{
    int i;
    for(i = 0; i < MAX_TEXTURES; i++)
    {
        if(strcmp(filename, _gfx_textures[i].filename) == 0)
        {
            return &_gfx_textures[i];
        }
    }

    // Texture not found, load it first
    _gfx_tex_load(filename);
    return _gfx_tex_get(filename);
}

void _gfx_tex_bind(const char *filename)
{
    _gfx_texture *tex = _gfx_tex_get(filename);
    glBindTexture(GL_TEXTURE_2D, tex->texID);

    glMatrixMode(GL_TEXTURE);
    glLoadIdentity();
    glScalef((float)(tex->srcW) / tex->texW, (float)(tex->srcH) / tex->texH, 0);
    glTranslatef(-.5 / tex->texW, -.5 / tex->texH, 0);
    glMatrixMode(GL_MODELVIEW);

    return;
}

void gfx_read_imagefile(const char* filename, int xTopLeft, int yTopLeft, int width, int height)
{
    _gfx_tex_bind(filename);
}

void _gfx_calcul_matrix_current()
{
    int i;
    mat3 temp, temp2;

    mat3_identity(temp);

    for( i = 0; i< _gfx_matrix_pile_top ; i++)
    {
        mat3_mult(temp2, temp, _gfx_matrix_pile[i]);
        mat3_copy(temp, temp2);
    }
    mat3_copy(_gfx_matrix_current, temp);
    return;
}

int gfx_set_resolution(GFX_Resolution res)
{
    GFX_Resolution old_res = _gfx_resolution;
    _gfx_resolution = res;

    SDL_QuitSubSystem(SDL_INIT_VIDEO);

    if(SDL_InitSubSystem(SDL_INIT_VIDEO) != 0)
    {
        printf("ERR:Couldn't initialize SDL video subsystem : %s\n", SDL_GetError());
        exit(EXIT_FAILURE);
    }

    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);

    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, res.multisampling == 0 ? 0 : 1);
    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, res.multisampling);

    if(SDL_SetVideoMode(res.width, res.height, 0, SDL_OPENGL) == 0)
    {
        // Go back to old parameters
        if(old_res.width == 0)
        {
            old_res.width = 640; old_res.height = 360; old_res.multisampling = 0;
        }
        _gfx_resolution = res = old_res;

        SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, old_res.multisampling == 0 ? 0 : 1);
        SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, old_res.multisampling);

        // Retry with old parameters
        if(SDL_SetVideoMode(old_res.width, old_res.height, 0, SDL_OPENGL) == 0)
        {
            printf("ERR:Couldn't create a window : %s\n", SDL_GetError());
            exit(EXIT_FAILURE);
        }
    }

    SDL_WM_SetCaption("Pong-Norris", 0);
    SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &_gfx_resolution.multisampling);

    glViewport(0, 0, res.width, res.height);
    _gfx_init_opengl();

    return 0;
}

void gfx_get_resolution(GFX_Resolution *res)
{
    memcpy(res, &_gfx_resolution, sizeof(GFX_Resolution));
}

void gfx_draw_set_color(int color)
{
    const int colors[16][3] =
    {
        {0, 0, 0},
        {68, 0, 184},
        {0, 206, 0},
        {0, 186, 183},
        {198, 0, 0},
        {206, 0, 184},
        {191, 110, 0},
        {182, 182, 182},
        {103, 103, 103},
        {132, 0, 255},
        {0, 255, 98},
        {0, 255, 255},
        {255, 88, 103},
        {255, 0, 255},
        {243, 255, 97},
        {255, 255, 255},
    } ;
    glColor3f(colors[color][0] / 255., colors[color][1] / 255., colors[color][2] / 255.);
}

void gfx_draw_set_bkcolor(int color) {}

//
// Initialization
//

void gfx_init(void)
{
    SDL_Init(0);
    _gfx_blur = 0;
    _gfx_matrix_pile_top = 0;
    mat3_identity(_gfx_matrix_pile[0]);
    _gfx_calcul_matrix_current();

    if(SDL_Init(0) != 0)
    {
        printf("ERR:Couldn't initialize SDL : %s\n", SDL_GetError());
        exit(EXIT_FAILURE);
    }
}

void _gfx_init_opengl(void)
{
    memset(_gfx_textures, 0, sizeof(_gfx_textures));

    glClear(GL_COLOR_BUFFER_BIT);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, 640, 360, 0, -1, 1);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glEnable (GL_BLEND);

    if(_gfx_resolution.multisampling) glEnable(GL_MULTISAMPLE);
}

void gfx_exit(void)
{
    SDL_QuitSubSystem(SDL_INIT_VIDEO);
    return;
}

void gfx_matrix_push(mat3 m)
{
    mat3_copy(_gfx_matrix_pile[_gfx_matrix_pile_top], m);
    _gfx_matrix_pile_top++;
    _gfx_calcul_matrix_current();
}

void gfx_matrix_pop(void)
{
    _gfx_matrix_pile_top--;
    _gfx_calcul_matrix_current();
}


void gfx_draw_line(float x1, float y1, float x2, float y2)
{
    vec2 p1, p2;
    vec2 result1, result2;

    p1[0] = x1;
    p1[1] = y1;
    p2[0] = x2;
    p2[1] = y2;

    mat3_mult_vec(result1, _gfx_matrix_current, p1);
    mat3_mult_vec(result2, _gfx_matrix_current, p2);

    glBegin(GL_LINES);
    glVertex2f(result1[0], result1[1]);
    glVertex2f(result2[0], result2[1]);
    glEnd();
}

void gfx_draw_rectangle(float x1, float y1, float x2, float y2)
{
    float coords[4][2];
    for(int i = 0; i < 4; i++)
    {
        vec2 p, result;

        // Find which corner to use
        switch(i)
        {
            case 0:
            {
                p[0] = x1;
                p[1] = y1;
                break;
            }
            case 1:
            {
                p[0] = x2;
                p[1] = y1;
                break;
            }
            case 2:
            {
                p[0] = x2;
                p[1] = y2;
                break;
            }
            case 3:
            {
                p[0] = x1;
                p[1] = y2;
                break;
            }
        }

        mat3_mult_vec(result, _gfx_matrix_current, p);

        coords[i][0] = result[0];
        coords[i][1] = result[1];
    }

    glBegin(GL_QUADS);
    for(int i = 0; i < 4; i++)
        glVertex2fv(coords[i]);
    glEnd();
}

void gfx_draw_circle(float r)
{
    // Precompute the circle to save CPU and battery time
    #define nb_segs 32
    static int cached_curve;
    static vec2 curve[nb_segs];

    if(cached_curve == 0)
    {
        cached_curve = 1;

        for(int i = 0; i < nb_segs; i++)
        {
            curve[i][0] = sin((float)(i) / (nb_segs-1) * 3.14159 * 2);
            curve[i][1] = cos((float)(i) / (nb_segs-1) * 3.14159 * 2);
        }
    }

    // Draw the circle
    for(int i = 0; i < nb_segs - 1; i++)
    {
        gfx_draw_line(curve[i][0]*r, curve[i][1]*r, curve[i+1][0]*r, curve[i+1][1]*r);
    }
    #undef nb_segs
    return;
}

void gfx_draw_text(float x, float y, const char *text)
{
    _gfx_texture *tex = _gfx_tex_get("data/font.bmp");
    _gfx_tex_bind("data/font.bmp");

    glEnable(GL_TEXTURE_2D);

    // For each character
    glBegin(GL_QUADS);
    for(; *text; text++)
    {
        float coords[2][2];
        vec2 pos, result;
        coords[0][0] = (((*text % 32) * 9.)) / tex->srcW;
        coords[0][1] = (((*text / 32) * 16.)) / tex->srcH;
        coords[1][0] = (((((*text) % 32)+1) * 9.)) / tex->srcW;
        coords[1][1] = (((((*text) / 32)+1) * 16.)) / tex->srcH;

        glTexCoord2f(coords[0][0], coords[0][1]);
        pos[0] = x; pos[1] = y;
        mat3_mult_vec(result, _gfx_matrix_current, pos);
        glVertex2fv(result);

        glTexCoord2f(coords[1][0], coords[0][1]);
        pos[0] = x+9; pos[1] = y;
        mat3_mult_vec(result, _gfx_matrix_current, pos);
        glVertex2fv(result);

        glTexCoord2f(coords[1][0], coords[1][1]);
        pos[0] = x+9; pos[1] = y+16;
        mat3_mult_vec(result, _gfx_matrix_current, pos);
        glVertex2fv(result);

        glTexCoord2f(coords[0][0], coords[1][1]);
        pos[0] = x; pos[1] = y+16;
        mat3_mult_vec(result, _gfx_matrix_current, pos);
        glVertex2fv(result);

        x += 9;
    }
    glEnd();

    glDisable(GL_TEXTURE_2D);
}

void gfx_scene_begin(void)
{
    if(_gfx_blur < 0.01)
    {
        glClear(GL_COLOR_BUFFER_BIT);
    }
    // Do full screen blur
    else
    {
        glBegin(GL_QUADS);
        glColor4f(0, 0, 0, 1-_gfx_blur);
        glVertex2f(0, 0);
        glVertex2f(640, 0);
        glVertex2f(640, 360);
        glVertex2f(0, 360);
        glEnd();
        glColor4f(1, 1, 1, 0);
    }
}

void gfx_scene_end()
{
    glEnable(GL_TEXTURE_2D);
    glDisable(GL_TEXTURE_2D);
    SDL_GL_SwapBuffers();
    time_sleep(1/120.);
}

void gfx_scene_set(void){}

void gfx_draw_image(const char *name, float x, float y)
{
    _gfx_texture *tex = _gfx_tex_get(name);
    _gfx_tex_bind(name);

    vec2 p[4] = {{x, y}, {x+tex->srcW, y}, {x+tex->srcW, y+tex->srcH}, {x, y+tex->srcH}} ;
    vec2 result[4];

    for(int i = 0; i < 4; i++)
    {
        mat3_mult_vec(result[i], _gfx_matrix_current, p[i]);
    }

    glEnable(GL_TEXTURE_2D);
    glBegin(GL_QUADS);

    glTexCoord2f(0, 0);
    glVertex2fv(result[0]);

    glTexCoord2f(1, 0);
    glVertex2fv(result[1]);

    glTexCoord2f(1, 1);
    glVertex2fv(result[2]);

    glTexCoord2f(0, 1);
    glVertex2fv(result[3]);

    glEnd();
    glDisable(GL_TEXTURE_2D);
}

int gfx_image_get_height(const char *name)
{
    _gfx_texture *tex = _gfx_tex_get(name);
    if(tex == 0) return 0;
    return tex->srcH;
}

int gfx_image_get_width(const char *name)
{
    _gfx_texture *tex = _gfx_tex_get(name);
    if(tex == 0) return 0;
    return tex->srcW;
}

//void gfx_read_imagefile(const char* filename, int xTopLeft, int yTopLeft, int width, int height){}
void gfx_clear_device(void){}
void gfx_set_text_style(int font, int direction, int charsize){}
void gfx_custom_color(int red, int green, int blue)
{
    glColor3f(red / 255., green / 255., blue / 255.);
}
int gfx_keyboard_state_change(void){return 0;}
int gfx_keyboard_input(void){return 0;}

void gfx_set_blur(float blur)
{
    if(blur > 0.85)
    {
        blur = 0.85;
    }
    _gfx_blur = blur;
}

#endif
